package org.codefinger.dao.impl;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import org.codefinger.dao.util.AbstractCache.ValueBuilder;
import org.codefinger.dao.util.ArrayIterator;
import org.codefinger.dao.util.IdentityCache;
import org.codefinger.dao.util.Lang;
import org.codefinger.dao.util.jdbc.JDBCUtil;
import org.codefinger.dao.util.jdbc.ResultSetGetter;
import org.codefinger.dao.util.magic.MagicAttribute;
import org.codefinger.dao.util.magic.MagicConstructor;
import org.codefinger.dao.util.magic.MagicPojo;

public class TableInfo {

	private static final ResultSetGetter			KEY_GETTER		= JDBCUtil.getRSGetter(long.class);

	private String									tableName;

	private String									primaryKey;

	private String[]								columns;

	private String[]								columnsExcKey;

	private String									deleteIdSQL;

	private String									deleteAllSQL;

	private String									updateSQL;

	private String									insertSQL;

	private String									selectSQL;

	private Set<String>								columnsSet;

	private IdentityCache<Class<?>, TablePojo<?>>	tablePojoChache	= new IdentityCache<Class<?>, TablePojo<?>>(16, new TablePojoBuilder(this));

	public TableInfo(String tableName, String primaryKey, String[] columns, String insertSQL) {
		super();
		this.tableName = tableName;
		this.primaryKey = primaryKey;
		this.columns = columns;
		String deleteAllSQL = Lang.joinStr(tableName.length() + 12, "DELETE FROM ", tableName);
		this.deleteAllSQL = deleteAllSQL;
		this.deleteIdSQL = Lang.joinStr(deleteAllSQL.length() + primaryKey.length() + 11,//
				deleteAllSQL, " WHERE ", primaryKey, " = ?");
		String[] columnsExcKey = new String[columns.length - 1];
		StringBuilder updateSQLBuilder = new StringBuilder("UPDATE ").append(tableName);
		StringBuilder selectSQLBuilder = new StringBuilder("SELECT ");
		Set<String> columnsSet = new HashSet<String>();
		int index = 0;
		boolean flag = false;
		for (String name : columns) {
			columnsSet.add(name);
			if (name.equals(primaryKey)) {
				continue;
			}
			columnsExcKey[index++] = name;
			if (flag) {
				updateSQLBuilder.append(",").append(name).append(" = ?");
				selectSQLBuilder.append(',').append(name);
			} else {
				updateSQLBuilder.append(" SET ").append(name).append(" = ?");
				selectSQLBuilder.append(name);
				flag = true;
			}
		}
		this.columnsSet = columnsSet;
		this.updateSQL = updateSQLBuilder.append(" WHERE ").append(primaryKey).append(" = ?").toString();
		this.selectSQL = selectSQLBuilder.append(" FROM ").append(tableName).append(" WHERE ").append(primaryKey).append(" = ?").toString();
		this.columnsExcKey = columnsExcKey;
		this.insertSQL = insertSQL;
	}

	public String getPrimaryKey() {
		return primaryKey;
	}

	public ArrayIterator<String> getColumns() {
		return new ArrayIterator<String>(columns);
	}

	public ArrayIterator<String> getColumnsExcKey() {
		return new ArrayIterator<String>(columnsExcKey);
	}

	public String getSelectSQL() {
		return selectSQL;
	}

	public String getDeleteIdSQL() {
		return deleteIdSQL;
	}

	public String getDeleteAllSQL() {
		return deleteAllSQL;
	}

	public String getTableName() {
		return tableName;
	}

	public String getUpdateSQL() {
		return updateSQL;
	}

	public String getInsertSQL() {
		return insertSQL;
	}

	@SuppressWarnings("unchecked")
	public <T> TablePojo<T> getTablePojo(Class<T> clazz) {
		return (TablePojo<T>) tablePojoChache.get(clazz);
	}

	public boolean contains(String column) {
		return columnsSet.contains(column);
	}

	public static class TablePojo<T> {

		private ValueGetter[]		valueGetters;

		private ValueSetter[]		valueSetters;

		private ValueGetter			keyGetter;

		private ValueSetter			keySetter;

		private MagicConstructor<T>	constructor;

		public TablePojo(MagicConstructor<T> constructor, ValueGetter[] valueGetters, ValueSetter[] valueSetters, ValueGetter keyGetter, ValueSetter keySetter) {
			super();
			this.constructor = constructor;
			this.valueGetters = valueGetters;
			this.valueSetters = valueSetters;
			this.keyGetter = keyGetter;
			this.keySetter = keySetter;
		}

		public ArrayIterator<ValueGetter> getValueGetters() {
			return new ArrayIterator<ValueGetter>(valueGetters);
		}

		public ArrayIterator<ValueSetter> getValueSetters() {
			return new ArrayIterator<ValueSetter>(valueSetters);
		}

		public Object getKeyValue(Object target) {
			return keyGetter.getValue(target);
		}

		public void setKeyValue(Object target, ResultSet resultSet) throws SQLException {
			keySetter.setValue(target, resultSet);
		}

		public void setKeyValue(Object target, Object value) {
			keySetter.setValue(target, value);
		}

		public T newInstance() {
			return constructor.newInstance();
		}

	}

	public static interface ValueSetter {

		public void setValue(Object target, Object value);

		public void setValue(Object target, ResultSet resultSet) throws SQLException;

	}

	public static interface ValueGetter {

		public Object getValue(Object target);

	}

	private static class TablePojoBuilder implements ValueBuilder<Class<?>, TablePojo<?>> {

		private TableInfo	tableInfo;

		public TablePojoBuilder(TableInfo tableInfo) {
			super();
			this.tableInfo = tableInfo;
		}

		@SuppressWarnings({ "unchecked", "rawtypes" })
		@Override
		public TablePojo<?> build(Class<?> pojoClass) {
			TableInfo tableInfo = this.tableInfo;
			MagicPojo<?> magicPojo = MagicPojo.getMagicPojo(pojoClass);
			Iterator<String> iterator = tableInfo.getColumnsExcKey();
			int length = tableInfo.columnsExcKey.length;
			ValueGetter[] valueGetters = new ValueGetter[length];
			ValueSetter[] valueSetters = new ValueSetter[length];
			int index = 0;
			while (iterator.hasNext()) {
				final MagicAttribute attribute = magicPojo.getMagicAttributeByUpperCase(iterator.next());
				if (attribute == null) {
					valueSetters[index] = new ValueSetter() {

						@Override
						public void setValue(Object target, ResultSet resultSet) throws SQLException {

						}

						@Override
						public void setValue(Object target, Object value) {

						}
					};
					valueGetters[index++] = new ValueGetter() {
						@Override
						public Object getValue(Object target) {
							return null;
						}
					};
				} else {
					final ResultSetGetter resultSetGetter = JDBCUtil.getRSGetter(attribute.getField().getType());
					valueGetters[index] = new ValueGetter() {
						@Override
						public Object getValue(Object target) {
							return attribute.getValue(target);
						}
					};
					final int i = index + 1;
					valueSetters[index++] = new ValueSetter() {
						@Override
						public void setValue(Object target, ResultSet resultSet) throws SQLException {
							Object value = resultSetGetter.getResult(resultSet, i);
							if (value == null) {
								return;
							}
							attribute.setValue(target, value);
						}

						@Override
						public void setValue(Object target, Object value) {
							attribute.setValue(target, value);
						}
					};
				}
			}
			final MagicAttribute attribute = magicPojo.getMagicAttributeByUpperCase(tableInfo.getPrimaryKey());
			ValueGetter keyGetter = attribute == null ? new ValueGetter() {
				@Override
				public Object getValue(Object target) {
					return null;
				}
			} : new ValueGetter() {

				@Override
				public Object getValue(Object target) {
					return attribute.getValue(target);
				}
			};

			ValueSetter valueSetter = attribute == null ? new ValueSetter() {

				@Override
				public void setValue(Object target, ResultSet resultSet) {
				}

				@Override
				public void setValue(Object target, Object value) {

				}

			} : new ValueSetter() {

				@Override
				public void setValue(Object target, ResultSet resultSet) throws SQLException {
					attribute.setValue(target, KEY_GETTER.getResult(resultSet, 1));
				}

				@Override
				public void setValue(Object target, Object value) {
					attribute.setValue(target, value);
				}
			};

			return new TablePojo(MagicConstructor.getMagicConstructor(pojoClass), valueGetters, valueSetters, keyGetter, valueSetter);
		}

	}

}
